﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
#if !WINDOWS_PHONE
using System.Linq;
#endif
#if NETFX_CORE
using Windows.UI.Xaml;
using Windows.UI.Core;
using Windows.Storage.Pickers;
using System.Threading.Tasks;
#else
#if !SILVERLIGHT
using System.Threading;
#endif
#if !SILVERLIGHT_4 && !WINDOWS_PHONE
using System.Windows;
#endif
#if SILVERLIGHT && !WINDOWS_PHONE
using System.Windows.Controls;
#endif
using System.Windows.Threading;
#if !SILVERLIGHT
using Microsoft.Win32;
#endif
#endif

namespace DaveSexton.Labs
{
#if NETFX_CORE
	public partial class WindowsStoreLab
#elif WINDOWS_PHONE
	public partial class PhoneLab
#elif SILVERLIGHT
	public partial class SilverlightLab
#else
	public partial class WindowsLab
#endif
	{
#if !SILVERLIGHT_4 && !WINDOWS_PHONE
		private static Window MainWindow
		{
			get
			{
#if NETFX_CORE
				return Window.Current;
#else
				var app = Application.Current;

				if (app == null)
				{
					return null;
				}

#if SILVERLIGHT
				if (!app.IsRunningOutOfBrowser)
				{
					return null;
				}
#endif

				return app.MainWindow;
#endif
			}
		}
#endif

#if NETFX_CORE
		private static string FormatFilterType(string filterType)
		{
			if (string.IsNullOrEmpty(filterType))
			{
				return filterType;
			}

			var dot = filterType.IndexOf('.');

			if (filterType.Length > dot + 1)
			{
				filterType = filterType.Substring(dot + 1);
		}

			if (filterType == "*" || dot == -1 || dot == filterType.Length - 1)
			{
				return "*";
			}
			else
			{
				return "." + filterType;
			}
		}
#endif

#if NETFX_CORE
		private static async Task<T> InvokeOnDispatcherAsync<T>(CoreDispatcher dispatcher, Func<Task<T>> function)
		{
			Contract.Requires(dispatcher != null);
			Contract.Requires(function != null);

			if (dispatcher.HasThreadAccess)
			{
				return await function().ConfigureAwait(false);
			}
			else
			{
				var result = new TaskCompletionSource<T>();

				await dispatcher.RunAsync(
					CoreDispatcherPriority.Normal,
					() => function().ContinueWith(
						task =>
						{
							switch (task.Status)
							{
								case TaskStatus.Canceled:
									result.SetCanceled();
									break;
								case TaskStatus.Faulted:
									result.SetException(task.Exception);
									break;
								default:
									result.SetResult(task.Result);
									break;
							}
						},
						TaskContinuationOptions.ExecuteSynchronously));

				return await result.Task.ConfigureAwait(false);
			}
		}
#elif !SILVERLIGHT
		private static T InvokeOnStaThread<T>(Func<T> function)
		{
			Contract.Requires(function != null);

			var result = default(T);

			using (var handle = new ManualResetEventSlim(false))
			{
				var thread = new Thread(() =>
				{
					result = function();

					handle.Set();
				});

				thread.SetApartmentState(ApartmentState.STA);
				thread.Start();

				handle.Wait();
			}

			return result;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Contracts", "Nonnull-62-0",
			Justification = "The Invoke method returns T from the specified function, which cannot return null if T is a value-type.")]
		private static T InvokeOnDispatcher<T>(Dispatcher dispatcher, Func<T> function)
		{
			Contract.Requires(function != null);

			if (dispatcher == null)
			{
				return Thread.CurrentThread.GetApartmentState() == ApartmentState.STA
					? function()
					: InvokeOnStaThread(function);
			}
			else
			{
				return dispatcher.CheckAccess()
					? function()
					: (T)dispatcher.Invoke(function);
			}
		}
#elif !WINDOWS_PHONE
		private static T InvokeOnDispatcher<T>(Dispatcher dispatcher, Func<T> function)
		{
			Contract.Requires(dispatcher != null);
			Contract.Requires(function != null);

			if (dispatcher.CheckAccess())
			{
				return function();
			}
			else
			{
				using (var handle = new System.Threading.ManualResetEvent(false))
				{
					var result = default(T);

					dispatcher.BeginInvoke(() =>
					{
						result = function();

						handle.Set();
					});

					handle.WaitOne();

					return result;
				}
			}
		}
#endif

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "title",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "initialDirectory",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
#if WINDOWS_PHONE
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "filter",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
#endif
#if NETFX_CORE
		internal static Task<IFile> UserInputFileToOpenAsync(string title, string filter, string initialDirectory, CoreDispatcher dispatcher)
#else
		internal static IFile UserInputFileToOpen(string title, string filter, string initialDirectory, Dispatcher dispatcher)
#endif
		{
#if SILVERLIGHT
			Contract.Requires(dispatcher != null);
#endif

#if !WINDOWS_PHONE
#if NETFX_CORE
			return InvokeOnDispatcherAsync(
				dispatcher,
				async () =>
#else
			return InvokeOnDispatcher(
					dispatcher,
					() =>
#endif
					{
#if NETFX_CORE
					PickerLocationId location;
					if (!Enum.TryParse(initialDirectory, true, out location))
					{
						location = PickerLocationId.DocumentsLibrary;
					}

					var dialog = new FileOpenPicker()
					{
						SettingsIdentifier = title,
						SuggestedStartLocation = location
					};

					if (string.IsNullOrWhiteSpace(filter))
					{
						dialog.FileTypeFilter.Add("*");
					}
					else
					{
						foreach (var filterType in filter.Split(';') ?? new string[0])
						{
							dialog.FileTypeFilter.Add(FormatFilterType(filterType));
						}
					}
#else
						var dialog = new OpenFileDialog()
						{
							Filter = string.IsNullOrWhiteSpace(filter) ? Properties.Resources.DefaultFileDialogFilter : filter,
							Multiselect = false,
#if !SILVERLIGHT_4
							InitialDirectory = initialDirectory,
#endif
#if !SILVERLIGHT
							CheckFileExists = true,
							CheckPathExists = true,
							ShowReadOnly = false,
							Title = string.IsNullOrWhiteSpace(title) ? Properties.Resources.DefaultOpenFileDialogTitle : title,
							ValidateNames = true
#endif
						};
#endif

						try
						{
#if NETFX_CORE
						var file = await dialog.PickSingleFileAsync();

						if (file != null)
#elif !SILVERLIGHT_4
							var window = MainWindow;

							if ((window == null ? dialog.ShowDialog() : dialog.ShowDialog(MainWindow)) ?? false)
#else
							if (dialog.ShowDialog() ?? false)
#endif
							{
#if NETFX_CORE
								return FileSystem.GetFile(file);
#elif !SILVERLIGHT
								return FileSystem.GetFile(dialog.FileName);
#else
								return FileSystem.GetFile(dialog.File);
#endif
							}
						}
						catch (System.Security.SecurityException)
						{
							/* SecurityException is thrown by ShowDialog when called in a Silverlight console lab that is running 
							 * inside a web browser because dialogs must be user-initiated; i.e., ShowDialog must be called within 
							 * the event handler for a UI event, such as a button click.
							 */
						}

						return null;
					});
#else
			throw new NotSupportedException();
#endif
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "title",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "initialDirectory",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
#if WINDOWS_PHONE
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "filter",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
#endif
#if NETFX_CORE
		internal static async Task<IList<IFile>> UserInputFilesToOpenAsync(string title, string filter, string initialDirectory, CoreDispatcher dispatcher)
#else
		internal static IList<IFile> UserInputFilesToOpen(string title, string filter, string initialDirectory, Dispatcher dispatcher)
#endif
		{
#if SILVERLIGHT
			Contract.Requires(dispatcher != null);
#endif
#if !NETFX_CORE
			Contract.Ensures(Contract.Result<IList<IFile>>() != null);
			Contract.Ensures(Contract.Result<IList<IFile>>().IsReadOnly);
			////Contract.Ensures(Contract.ForAll(Contract.Result<IList<IFile>>(), file => file != null));
#endif

#if !WINDOWS_PHONE
#if NETFX_CORE
			var files = await InvokeOnDispatcherAsync(
				dispatcher,
				async () =>
#else
			var files = InvokeOnDispatcher(
					dispatcher,
					() =>
#endif
					{
#if NETFX_CORE
					PickerLocationId location;
					if (!Enum.TryParse(initialDirectory, true, out location))
					{
						location = PickerLocationId.DocumentsLibrary;
					}

					var dialog = new FileOpenPicker()
					{
						SettingsIdentifier = title,
						SuggestedStartLocation = location
					};

					if (string.IsNullOrWhiteSpace(filter))
					{
						dialog.FileTypeFilter.Add("*");
					}
					else
					{
						foreach (var filterType in filter.Split(';') ?? new string[0])
						{
							dialog.FileTypeFilter.Add(FormatFilterType(filterType));
						}
					}
#else
						var dialog = new OpenFileDialog()
						{
							Filter = string.IsNullOrWhiteSpace(filter) ? Properties.Resources.DefaultFileDialogFilter : filter,
							Multiselect = true,
#if !SILVERLIGHT_4
							InitialDirectory = initialDirectory,
#endif
#if !SILVERLIGHT
							CheckFileExists = true,
							CheckPathExists = true,
							ShowReadOnly = false,
							Title = string.IsNullOrWhiteSpace(title) ? Properties.Resources.DefaultOpenMultipleFilesDialogTitle : title,
							ValidateNames = true
#endif
						};
#endif

						try
						{
#if NETFX_CORE
						var results = await dialog.PickMultipleFilesAsync();

						if (results != null)
#elif !SILVERLIGHT_4
							var window = MainWindow;

							if ((window == null ? dialog.ShowDialog() : dialog.ShowDialog(MainWindow)) ?? false)
#else
							if (dialog.ShowDialog() ?? false)
#endif
							{
#if NETFX_CORE
								return results.Select(FileSystem.GetFile);
#elif !SILVERLIGHT
								return dialog.FileNames.Select(FileSystem.GetFile);
#else
								return dialog.Files.Select(FileSystem.GetFile);
#endif
							}
						}
						catch (System.Security.SecurityException)
						{
							/* SecurityException is thrown by ShowDialog when called in a Silverlight console lab that is running 
							 * inside a web browser because dialogs must be user-initiated; i.e., ShowDialog must be called within 
							 * the event handler for a UI event, such as a button click.
							 */
						}

						return null;
					});

			IList<IFile> list;

			if (files == null)
			{
				list = new List<IFile>(0).AsReadOnly();
			}
			else
			{
				list = files.ToList().AsReadOnly();
			}

			Contract.Assume(list.IsReadOnly);

			return list;
#else
			throw new NotSupportedException();
#endif
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "title",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "initialDirectory",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "canCreate",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
#if WINDOWS_PHONE
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "filter",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "defaultExtension",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
#endif
#if NETFX_CORE
		internal static Task<FileStream> UserInputFileToSaveAsync(string title, string filter, string initialDirectory, string defaultExtension, bool canCreate, CoreDispatcher dispatcher)
#else
		internal static FileStream UserInputFileToSave(string title, string filter, string initialDirectory, string defaultExtension, bool canCreate, Dispatcher dispatcher)
#endif
		{
#if SILVERLIGHT
			Contract.Requires(dispatcher != null);
#endif

#if !WINDOWS_PHONE
#if NETFX_CORE
			return InvokeOnDispatcherAsync(
				dispatcher,
				async () =>
#else
			return InvokeOnDispatcher(
					dispatcher,
					() =>
#endif
					{
#if NETFX_CORE
					PickerLocationId location;
					if (!Enum.TryParse(initialDirectory, true, out location))
					{
						location = PickerLocationId.DocumentsLibrary;
					}

					var dialog = new FileSavePicker()
					{
						SettingsIdentifier = title,
						SuggestedStartLocation = location
					};

					if (defaultExtension != null)
					{
						dialog.DefaultFileExtension = defaultExtension;
					}

					if (string.IsNullOrWhiteSpace(filter))
					{
						filter = "Text Files (*.txt)|*.txt";
					}

					var filterTypeElements = filter.Split('|');

					var left = filterTypeElements.Where((_, i) => i % 2 == 0);
					var right = filterTypeElements.Where((_, i) => i % 2 != 0);

					foreach (var filterType in left.Zip(right, (name, types) => new { name, types }))
					{
						dialog.FileTypeChoices.Add(filterType.name, filterType.types.Split(';').Select(FormatFilterType).ToList());
					}
#else
						var dialog = new SaveFileDialog()
						{
							DefaultExt = string.IsNullOrWhiteSpace(defaultExtension) ? null : defaultExtension,
							Filter = string.IsNullOrWhiteSpace(filter) ? Properties.Resources.DefaultFileDialogFilter : filter,
#if !SILVERLIGHT
							AddExtension = !string.IsNullOrWhiteSpace(defaultExtension),
							CheckFileExists = !canCreate,
							CheckPathExists = !canCreate,
							CreatePrompt = false,
							InitialDirectory = initialDirectory,
							OverwritePrompt = canCreate,
							Title = string.IsNullOrWhiteSpace(title) ? Properties.Resources.DefaultSaveFileDialogTitle : title,
							ValidateNames = true
#endif
						};
#endif

						try
						{
#if NETFX_CORE
						var file = await dialog.PickSaveFileAsync();

						if (file != null)
#elif !SILVERLIGHT_4
							var window = MainWindow;

							if ((window == null ? dialog.ShowDialog() : dialog.ShowDialog(MainWindow)) ?? false)
#else
							if (dialog.ShowDialog() ?? false)
#endif
							{
#if NETFX_CORE
								var stream = await file.OpenStreamForWriteAsync().ConfigureAwait(false);

								return new FileStream(file.Path, stream);
#else
								return (FileStream)dialog.OpenFile();
#endif
							}
						}
						catch (System.Security.SecurityException)
						{
							/* SecurityException is thrown by ShowDialog when called in a Silverlight console lab that is running 
							 * inside a web browser because dialogs must be user-initiated; i.e., ShowDialog must be called within 
							 * the event handler for a UI event, such as a button click.
							 */
						}

						return null;
					});
#else
			throw new NotSupportedException();
#endif
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "title",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "canCreate",
			Justification = "Required for cross-platform parity although Silverlight has no use for this parameter.")]
#if NETFX_CORE
		internal static Task<IFolder> UserInputFolderAsync(string title, bool canCreate, CoreDispatcher dispatcher)
#else
		internal static IFolder UserInputFolder(string title, bool canCreate, Dispatcher dispatcher)
#endif
		{
#if SILVERLIGHT
			Contract.Requires(dispatcher != null);
#endif

#if !SILVERLIGHT
#if NETFX_CORE
			return InvokeOnDispatcherAsync(
				dispatcher,
				async () =>
#else
			return InvokeOnDispatcher(
				dispatcher,
				() =>
#endif
				{
#if NETFX_CORE
					var dialog = new FolderPicker()
					{
						SettingsIdentifier = title,
						SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
						FileTypeFilter =
						{
							"*"
						}
					};

					var folder = await dialog.PickSingleFolderAsync();

					if (folder != null)
					{
						return FileSystem.GetFolder(folder);
					}
#else
					using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
					{
						dialog.Description = string.IsNullOrWhiteSpace(title) ? Properties.Resources.DefaultFolderDialogTitle : title;
						dialog.ShowNewFolderButton = canCreate;

						var result = dialog.ShowDialog(WindowsLabController.CurrentNativeWindow);

						if (result == System.Windows.Forms.DialogResult.OK)
						{
							return FileSystem.GetFolder(dialog.SelectedPath);
						}
					}
#endif

					return null;
				});
#else
			throw new NotSupportedException();
#endif
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed",
			Justification = "All of the supported languages allow optional parameters.")]
#if NETFX_CORE
		protected Task<IFile> UserInputFileToOpenAsync(string title = null, string filter = null, string initialDirectory = null)
#else
		protected IFile UserInputFileToOpen(string title = null, string filter = null, string initialDirectory = null)
#endif
		{
#if NETFX_CORE
			return UserInputFileToOpenAsync(title, filter, initialDirectory, Dispatcher);
#else
			return UserInputFileToOpen(title, filter, initialDirectory, Dispatcher);
#endif
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed",
			Justification = "All of the supported languages allow optional parameters.")]
#if NETFX_CORE
		protected Task<IList<IFile>> UserInputFilesToOpenAsync(string title = null, string filter = null, string initialDirectory = null)
#else
		protected IList<IFile> UserInputFilesToOpen(string title = null, string filter = null, string initialDirectory = null)
#endif
		{
#if !NETFX_CORE
			Contract.Ensures(Contract.Result<IList<IFile>>() != null);
			Contract.Ensures(Contract.Result<IList<IFile>>().IsReadOnly);
			////Contract.Ensures(Contract.ForAll(Contract.Result<IList<IFile>>(), file => file != null)); 
#endif

#if NETFX_CORE
			return UserInputFilesToOpenAsync(title, filter, initialDirectory, Dispatcher);
#else
			return UserInputFilesToOpen(title, filter, initialDirectory, Dispatcher);
#endif
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed",
			Justification = "All of the supported languages allow optional parameters.")]
#if NETFX_CORE
		protected Task<FileStream> UserInputFileToSaveAsync(string title = null, string filter = null, string initialDirectory = null, string defaultExtension = null, bool canCreate = true)
#else
		protected FileStream UserInputFileToSave(string title = null, string filter = null, string initialDirectory = null, string defaultExtension = null, bool canCreate = true)
#endif
		{
#if NETFX_CORE
			return UserInputFileToSaveAsync(title, filter, initialDirectory, defaultExtension, canCreate, Dispatcher);
#else
			return UserInputFileToSave(title, filter, initialDirectory, defaultExtension, canCreate, Dispatcher);
#endif
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed",
			Justification = "All of the supported languages allow optional parameters.")]
#if NETFX_CORE
		protected Task<IFolder> UserInputFolderAsync(string title = null, bool canCreate = true)
#else
		protected IFolder UserInputFolder(string title = null, bool canCreate = true)
#endif
		{
#if NETFX_CORE
			return UserInputFolderAsync(title, canCreate, Dispatcher);
#else
			return UserInputFolder(title, canCreate, Dispatcher);
#endif
		}
	}
}